using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using System.Security.Cryptography;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace Noftware.Com.Cryptography
{
    /// <summary>
    /// Encrypt and Decrypt strings of text.
    /// </summary>
    [Serializable]
    public class Cryptography : ISerializable
    {
        #region Constructors

        /// <summary>
        /// This constructor is used to initiate the class
        /// and set the salt value for Md5Encrypt() and Md5Decrypt(),
        /// as well as RijndaelEncrypt() and RijndaelDecrypt().
        /// Do not use this constructor for any other methods.
        /// </summary>
        /// <param name="key">Key value.</param>
        public Cryptography(string key)
        {
            // Set the key
            _key = key;

            // The salt value used to strengthen the encryption for Rijndael.
            SALT = Encoding.ASCII.GetBytes(_key.Length.ToString());
        }

        /// <summary>
        /// This constructor is used to initiate the class
        /// for use for AsciiToHex() and HexToAscii().
        /// Do not use this constructor for any other methods.
        /// </summary>
        public Cryptography() { }

        /// <summary>
        /// This constructor is used to initiate the class
        /// and set the key value for XorString().
        /// Do not use this constructor for any other methods.
        /// </summary>
        /// <param name="xorKey">XOR key value.</param>
        public Cryptography(int xorKey)
        {
            // Set the XOR key
            _xorKey = xorKey;
        }

        //-----------------------------------
        /// <summary>
        /// DeSerialization constructor.
        /// </summary>
        /// <param name="info">Serialization information.</param>
        /// <param name="context">Data stream to deserialize.</param>
        protected Cryptography(SerializationInfo info, StreamingContext context)
        {
            // Deserialixe the instance data
            _key = (string)info.GetValue("_key", typeof(string));
            _xorKey = (int)info.GetValue("_xorKey", typeof(int));

        } // [ctor]


        //////////////////////////////////////////////////////////////////////////
        // ISerializable methods
        //

        /// <summary>
        /// Get the data being serialized.
        /// </summary>
        /// <param name="xoInfo">Serialization information.</param>
        /// <param name="xoContext">Data stream to deSerialize.</param>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void GetObjectData(SerializationInfo xoInfo, StreamingContext xoContext)
        {
            // Serialize the instance data
            xoInfo.AddValue("_key", _key);
            xoInfo.AddValue("_xorKey", _xorKey);

        } // [method] GetObjectData()

        #endregion constructor

        #region Public methods

        /// <summary>
        /// XOR's a string to either encrypt it or decrypt.
        /// </summary>
        /// <param name="text">String to be XOR'ed.</param>
        /// <returns>XOR'ed string.</returns>
        /// <remarks>Example referenced here: http://www.vcskicks.com/xor-encryption.php.</remarks>
        public string XorString(string text)
        {
            // Holds the return string
            var sbNewText = new StringBuilder();

            for (int i = 0; i < text.Length; i++)
            {
                int charValue = text[i];    // Get the ASCII value of the character
                charValue ^= _xorKey;       // XOR the value

                sbNewText.Append((char)charValue); // Convert back to string
            }

            return sbNewText.ToString();
        }

        /// <summary>
        /// Encrypt a string.
        /// </summary>
        /// <param name="text">String being encrypted.</param>
        /// <returns>string</returns>
        public string Md5Encrypt(string text)
        {
            string original;
            string encrypted = string.Empty;
            string password;
            TripleDESCryptoServiceProvider des;
            MD5CryptoServiceProvider hashmd5;
            byte[] pwdhash;
            byte[] buff;

            try
            {
                // Create a secret password. The password is used to encrypt
                // and decrypt strings. Without the password, the encrypted
                // string cannot be decrypted and is just garbage. You must
                // use the same password to decrypt an encrypted string as the
                // string was originally encrypted with.
                password = _key;

                // The string to encrypt
                original = ((text == null) ? string.Empty : text);

                // Ensure something was passed in
                if (original.Length > 0)
                {
                    // Generate an MD5 hash from the password.
                    // A hash is a one way encryption meaning once you generate
                    // the hash, you cant derive the password back from it.
                    hashmd5 = new MD5CryptoServiceProvider();
                    pwdhash = hashmd5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(password));
                    hashmd5 = null;

                    // Implement DES3 encryption.
                    des = new TripleDESCryptoServiceProvider();

                    // The key is the secret password hash.
                    des.Key = pwdhash;

                    // The mode is the block cipher mode which is basically the
                    // details of how the encryption will work. There are several
                    // kinds of ciphers available in DES3 and they all have benefits
                    // and drawbacks. Here the Electronic Codebook cipher is used
                    // which means that a given bit of text is always encrypted
                    // exactly the same when the same password is used.
                    // CBC, CFB
                    des.Mode = CipherMode.ECB;

                    // ----- Encrypt an Un-encrypted string ------------
                    // The original string, which needs encrypted, must be in byte
                    // array form to work with the des3 class. Everything will because
                    // most encryption works at the byte level so you'll find that
                    // the class takes in byte arrays and returns byte arrays and
                    // you'll be converting those arrays to strings.
                    buff = ASCIIEncoding.ASCII.GetBytes(original);

                    // Encrypt the byte buffer representation of the original string
                    // and base64 encode the encrypted string. The reason the encrypted
                    // bytes are being base64 encoded as a string is the encryption will
                    // have created some weird characters in there. Base64 encoding
                    // provides a platform independent view of the encrypted string
                    // and can be sent as a plain text string to wherever.
                    encrypted = Convert.ToBase64String(des.CreateEncryptor().TransformFinalBlock(buff, 0, buff.Length));

                    // Cleanup
                    des = null;

                }  // if (original.Length > 0)

                // Return the encrypted string
                return encrypted;
            }
            catch (Exception ex)
            {
                // String encryption error
                throw new System.ApplicationException("String encryption failed.", ex);
            }
        }

        /// <summary>
        /// Decrypt an encrypted string.
        /// </summary>
        /// <param name="text">String being decrypted.</param>
        /// <returns>string</returns>
        public string Md5Decrypt(string text)
        {
            string encrypted = string.Empty;
            string password;
            string decrypted = string.Empty;
            TripleDESCryptoServiceProvider des;
            MD5CryptoServiceProvider hashmd5;
            byte[] pwdhash;
            byte[] buff;

            try
            {
                // Create a secret password. The password is used to encrypt
                // and decrypt strings. Without the password, the encrypted
                // string cannot be decrypted and is just garbage. You must
                // use the same password to decrypt an encrypted string as the
                // string was originally encrypted with.
                password = _key;

                // The string to encrypt
                encrypted = ((text == null) ? string.Empty : text);

                // Ensure something was passed in
                if (encrypted.Length > 0)
                {

                    // Generate an MD5 hash from the password.
                    // A hash is a one way encryption meaning once you generate
                    // the hash, you cant derive the password back from it.
                    hashmd5 = new MD5CryptoServiceProvider();
                    pwdhash = hashmd5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(password));
                    hashmd5 = null;

                    // Implement DES3 encryption.
                    des = new TripleDESCryptoServiceProvider();

                    // The key is the secret password hash.
                    des.Key = pwdhash;

                    // The mode is the block cipher mode which is basically the
                    // details of how the encryption will work. There are several
                    // kinds of ciphers available in DES3 and they all have benefits
                    // and drawbacks. Here the Electronic Codebook cipher is used
                    // which means that a given bit of text is always encrypted
                    // exactly the same when the same password is used.
                    // CBC, CFB
                    des.Mode = CipherMode.ECB;

                    // ----- Decrypt an Encrypted string ------------
                    // Whenever you decrypt a string, you must do everything you
                    // did to encrypt the string, but in reverse order. To encrypt,
                    // first a normal string was des3 encrypted into a byte array
                    // and then base64 encoded for reliable transmission. So, to
                    // decrypt this string, first the base64 encoded string must be
                    // decoded so that just the encrypted byte array remains.
                    buff = Convert.FromBase64String(encrypted);

                    // Decrypt DES 3 encrypted byte buffer and return ASCII string
                    decrypted = ASCIIEncoding.ASCII.GetString(des.CreateDecryptor().TransformFinalBlock(buff, 0, buff.Length));

                    // Cleanup
                    des = null;

                }  // if (original.Length > 0)

                // Return the encrypted string
                return decrypted;
            }
            catch (Exception ex)
            {
                //  String decryption error
                throw new System.ApplicationException("String decryption failed.", ex);
            }
        }

        /// <summary>
        /// The salt value used to strengthen the encryption.
        /// </summary>
        private byte[] SALT;

        /// <summary>
        /// Encrypts any string using the Rijndael algorithm.
        /// </summary>
        /// <param name="inputText">The string to encrypt.</param>
        /// <returns>A Base64 encrypted string.</returns>
        public string RijndaelEncrypt(string inputText)
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            byte[] plainText = Encoding.Unicode.GetBytes(inputText);
            PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(_key, SALT);

            using (ICryptoTransform encryptor = rijndaelCipher.CreateEncryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16)))
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(plainText, 0, plainText.Length);
                        cryptoStream.FlushFinalBlock();
                        // return "?" + PARAMETER_NAME + Convert.ToBase64String(memoryStream.ToArray());

                        return Convert.ToBase64String(memoryStream.ToArray());

                    } // using (CryptoStream cryptoStream =...

                } // using (MemoryStream memoryStream...

            } // using (ICryptoTransform encryptor = rijnda...
        }

        /// <summary>
        /// Decrypts a previously encrypted Rijndael string.
        /// </summary>
        /// <param name="inputText">The encrypted string to decrypt.</param>
        /// <returns>A decrypted string.</returns>
        public string RijndaelDecrypt(string inputText)
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            byte[] encryptedData = Convert.FromBase64String(inputText);
            PasswordDeriveBytes secretKey = new PasswordDeriveBytes(_key, SALT);

            using (ICryptoTransform decryptor = rijndaelCipher.CreateDecryptor(secretKey.GetBytes(32), secretKey.GetBytes(16)))
            {
                using (MemoryStream memoryStream = new MemoryStream(encryptedData))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                    {
                        byte[] plainText = new byte[encryptedData.Length];
                        int decryptedCount = cryptoStream.Read(plainText, 0, plainText.Length);
                        return Encoding.Unicode.GetString(plainText, 0, decryptedCount);

                    } // using (CryptoStream cryptoStream =...

                } // using (MemoryStream memoryStream...

            } // using (ICryptoTransform decryptor...
        }

        /// <summary>
        /// Converts a hex string to its ASCII equivalent.
        /// </summary>
        /// <param name="data">String to be converted.</param>
        /// <returns>ASCII representation.</returns>
        public string HexToAscii(string data)
        {
            // Each character converted to ASCII
            string asciiChar = string.Empty;

            // StringBuilder to hold the return text
            var asciiString = new System.Text.StringBuilder();

            while (data.Length > 0)
            {
                // Convert the hex back to an ASCII
                asciiChar = System.Convert.ToChar(System.Convert.ToUInt32(data.Substring(0, 2), 16)).ToString();

                // Add to the string
                asciiString.Append(asciiChar);

                // Remove that hex char for the next iteration
                data = data.Substring(2, data.Length - 2);
            }

            return asciiString.ToString();
        }

        /// <summary>
        /// Converts an ASCII string to its Hex equivalent.
        /// </summary>
        /// <param name="data">String to be converted.</param>
        /// <returns>Hex representation.</returns>
        public string AsciiToHex(string data)
        {
            // Each character converted to a hex
            string hexChar;

            // StringBuilder to hold the return text
            var hexString = new System.Text.StringBuilder();

            // Each character from the string
            char c;

            while (data.Length > 0)
            {
                // Get as a char
                c = data[0];
                // Convert to an int and then to a hex string
                hexChar = ((int)c).ToString("X");

                // Add to the string
                hexString.Append(hexChar.ToUpper());

                // Remove that ASCII char for the next iteration
                data = data.Substring(1, data.Length - 1);
            }

            return hexString.ToString();
        }

        #endregion Public methods

        #region Private variables

        /// <summary>
        /// This is the Key needed to encrypt and decrypt the string.
        /// </summary>
        private string _key = null;

        /// <summary>
        /// Key for XOR'ing a string.
        /// </summary>
        private int _xorKey = 0;

        #endregion Private variables

    } // [class] Cryptography
}
